﻿using System;
using System.Linq.Expressions;
using System.Reflection;

using Microsoft.CSharp.RuntimeBinder;

using Binder = Microsoft.CSharp.RuntimeBinder.Binder;

namespace BrightSword.Feber.Core
{
    ///<summary>
    ///  This is a set of utility methods for property access on dynamic objects, exposed as a set of extension methods on the <see
    ///   cref="PropertyInfo" /> class.
    ///</summary>
    ///<summary>
    ///  This is a set of utility methods for property access on dynamic objects, exposed as a set of extension methods on the <see
    ///   cref="PropertyInfo" /> class.
    ///</summary>
    public static class DynamicExpressionUtilities
    {
        private const CSharpArgumentInfoFlags C_VALUE_FLAGS = CSharpArgumentInfoFlags.Constant | CSharpArgumentInfoFlags.UseCompileTimeType;

        private const CSharpArgumentInfoFlags C_THIS_FLAGS = CSharpArgumentInfoFlags.None;

        private const CSharpBinderFlags C_BINDER_SET_FLAGS = CSharpBinderFlags.InvokeSpecialName | CSharpBinderFlags.ResultDiscarded;

        private const CSharpBinderFlags C_BINDER_GET_FLAGS = CSharpBinderFlags.InvokeSpecialName;

        private static readonly CSharpArgumentInfo _thisArgument = CSharpArgumentInfo.Create(C_THIS_FLAGS,
                                                                                             null);

        private static readonly CSharpArgumentInfo _valueArgument = CSharpArgumentInfo.Create(C_VALUE_FLAGS,
                                                                                              null);

        /// <summary>
        ///   Accessing properties on dynamic objects is more involved than traditional reflection. This function returns an expression which, when evaluated, performs the property 'get' on the dynamic object
        /// </summary>
        /// <typeparam name="T"> The type on which the property <paramref name="propertyInfo" /> is defined </typeparam>
        /// <param name="parameterExpression"> An Expression representing the object parameter to the property mutator (i.e the 'this' parameter to the lambda) </param>
        /// <param name="propertyInfo"> The propertyInfo specifying the name and type of the desired property to change </param>
        /// <returns> An expression representing { (PropertyType) instance.Property } </returns>
        public static Expression GetDynamicPropertyAccessorExpression<T>(this ParameterExpression parameterExpression,
                                                                         PropertyInfo propertyInfo)
        {
            return parameterExpression.GetDynamicPropertyAccessorExpression<T>(propertyInfo.Name,
                                                                               propertyInfo.PropertyType);
        }

        /// <summary>
        ///   Accessing properties on dynamic objects is more involved than traditional reflection. This function returns an expression which, when evaluated, performs the property 'get' on the dynamic object
        /// </summary>
        /// <typeparam name="T"> The type on which the property <paramref name="propertyName" /> is defined </typeparam>
        /// <param name="propertyName"> The name of the desired property to change </param>
        /// <param name="propertyType"> The type of the desired property to change </param>
        /// <param name="parameterExpression"> An Expression representing the object parameter to the property mutator (i.e the 'this' parameter to the lambda) </param>
        /// <returns> An expression representing { (PropertyType) instance.Property } </returns>
        public static Expression GetDynamicPropertyAccessorExpression<T>(this ParameterExpression parameterExpression,
                                                                         string propertyName,
                                                                         Type propertyType)
        {
            var binder = Binder.GetMember(C_BINDER_GET_FLAGS,
                                          propertyName,
                                          typeof (T),
                                          new[]
                                          {
                                              _thisArgument
                                          });
            return Expression.Convert(Expression.Dynamic(binder,
                                                         typeof (object),
                                                         parameterExpression),
                                      propertyType);
        }

        /// <summary>
        ///   Mutating properties on dynamic objects is more involved than traditional reflection. This function returns an expression which, when evaluated, performs the property 'set' on the dynamic object with the given value
        /// </summary>
        /// <typeparam name="T"> The type on which the property <paramref name="propertyInfo" /> is defined </typeparam>
        /// <param name="parameterExpression"> An Expression representing the object parameter to the property mutator (i.e the 'this' parameter to the lambda) </param>
        /// <param name="propertyInfo"> The propertyInfo specifying the name and type of the desired property to change </param>
        /// <param name="valueExpression"> An Expression representing the value paramter to the property mutator (i.e the 'value' parameter to the lambda) </param>
        /// <returns> An expression representing { instance.Property = value } </returns>
        public static Expression GetDynamicPropertyMutatorExpression<T>(this ParameterExpression parameterExpression,
                                                                        PropertyInfo propertyInfo,
                                                                        Expression valueExpression)
        {
            return parameterExpression.GetDynamicPropertyMutatorExpression<T>(propertyInfo.Name,
                                                                              valueExpression);
        }

        /// <summary>
        ///   Mutating properties on dynamic objects is more involved than traditional reflection. This function returns an expression which, when evaluated, performs the property 'set' on the dynamic object with the given value
        /// </summary>
        /// <typeparam name="T"> The type on which the property <paramref name="propertyName" /> is defined </typeparam>
        /// <param name="parameterExpression"> An Expression representing the object parameter to the property mutator (i.e the 'this' parameter to the lambda) </param>
        /// <param name="propertyName"> The name of the desired property to change </param>
        /// <param name="valueExpression"> An Expression representing the value paramter to the property mutator (i.e the 'value' parameter to the lambda) </param>
        /// <returns> An expression representing { instance.Property = value } </returns>
        public static Expression GetDynamicPropertyMutatorExpression<T>(this ParameterExpression parameterExpression,
                                                                        string propertyName,
                                                                        Expression valueExpression)
        {
            var binder = Binder.SetMember(C_BINDER_SET_FLAGS,
                                          propertyName,
                                          typeof (T),
                                          new[]
                                          {
                                              _thisArgument, _valueArgument
                                          });

            return Expression.Dynamic(binder,
                                      typeof (object),
                                      parameterExpression,
                                      valueExpression);
        }
    }
}